package cn.frank.framework.king.conf.aspect.vlog;

import cn.frank.framework.king.util.ServletUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.core.io.InputStreamSource;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author frank
 * @ClassName: InterceptorForRest    Rest拦截器
 * @Description:
 * @email renjiangyang@163.com
 * @date 2021年8月19日 下午4:57:22
 * @Copyright: 2028 renjiangyang  All rights reserved.
 */
@Component
@Aspect
@Slf4j
public class ControllerLogHandler {


    @Around(value = "execution(* cn.frank.framework.king.*.*Controller.*(..))")
    public Object aroundMethod(ProceedingJoinPoint point) throws Throwable {
        printRequest(point);
        return printResponse(point);
    }

    public Object printResponse(ProceedingJoinPoint point) throws Throwable {
        long startNs = System.nanoTime();
        HttpServletRequest request = ServletUtil.getHttpServletRequest();
        String requestUrl = Objects.requireNonNull(request).getRequestURI();
        String requestMethod = request.getMethod();
        // String methodName = point.getSignature().getName();

        // 日志模版
        StringBuilder template = new StringBuilder(200);
        template.append("\n\n");
        template.append("===============  Response Start  ================\n");

        // 日志参数
        List<Object> templateArgs = new ArrayList<>();

        // 获取方法上的 @ApiOperation 注解
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
        String apiOperationValue = apiOperation != null ? apiOperation.value() : "";



        // 打印路由
        long tookMs = Duration.ofNanos(System.nanoTime() - startNs).toMillis();
        template.append("<=== {}: {} ({} ms)\n");
        templateArgs.add(requestMethod);
        templateArgs.add(requestUrl);
        templateArgs.add(tookMs);

        // 打印返回结构体
        if (!apiOperationValue.isEmpty()) {
            template.append("===  API Operation ===  {}\n");
            templateArgs.add(apiOperationValue);
        }
        Object result = point.proceed();
        template.append("===  Result     ===  {}\n");
        templateArgs.add(JSONUtil.toJsonStr(result));


        template.append("===============   Response End   ================\n");
        log.info(template.toString(), templateArgs.toArray());
        return result;
    }

    public void printRequest(ProceedingJoinPoint point) {
        HttpServletRequest request = ServletUtil.getHttpServletRequest();
        String requestUrl = Objects.requireNonNull(request).getRequestURI();
        String requestMethod = request.getMethod();
        String methodName = point.getSignature().getName();

        // 日志模版
        StringBuilder template = new StringBuilder(300);
        template.append("\n\n");
        template.append("================  Request Start  ================\n");

        // 日志参数
        List<Object> templateArgs = new ArrayList<>();

        // 打印路由
        template.append("===> {}: {} {}\n");
        templateArgs.add(requestMethod);
        templateArgs.add(requestUrl);
        templateArgs.add(methodName);

        // 获取方法上的 @ApiOperation 注解
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
        String apiOperationValue = apiOperation != null ? apiOperation.value() : "";

        // 打印 @ApiOperation 注解值
        if (!apiOperationValue.isEmpty()) {
            template.append("===  API Operation ===  {}\n");
            templateArgs.add(apiOperationValue);
        }

        // 打印请求头
        Enumeration<String> headers = request.getHeaderNames();
        while (headers.hasMoreElements()) {
            String headerName = headers.nextElement();
            String headerValue = request.getHeader(headerName);
            template.append("=== Headers    ===  {}: {}\n");
            templateArgs.add(headerName);
            templateArgs.add(headerValue);
        }

        // 打印请求体
        MethodSignature ms = (MethodSignature) point.getSignature();
        Method methodSignature = ms.getMethod();
        Object[] args = point.getArgs();

        // 请求参数处理
        final Map<String, Object> paraMap = new HashMap<>(16);
        // 一次请求只能有一个 request body
        Object requestBodyValue = null;
        for (int i = 0; i < args.length; i++) {
            // 读取方法参数
            Parameter methodParam = methodSignature.getParameters()[i];
            // PathVariable 参数跳过
            PathVariable pathVariable = methodParam.getAnnotation(PathVariable.class);
            if (pathVariable != null) {
                continue;
            }
            RequestBody requestBody = methodParam.getAnnotation(RequestBody.class);
            String parameterName = methodParam.getName();
            Object value = args[i];

            // 如果是body的json则是对象
            if (requestBody != null) {
                requestBodyValue = value;
                continue;
            }

            // 处理 参数
            if (value instanceof HttpServletRequest) {
                paraMap.putAll(((HttpServletRequest) value).getParameterMap());
                continue;
            } else if (value instanceof WebRequest) {
                paraMap.putAll(((WebRequest) value).getParameterMap());
                continue;
            } else if (value instanceof HttpServletResponse) {
                continue;
            } else if (value instanceof MultipartFile) {
                MultipartFile multipartFile = (MultipartFile) value;
                String name = multipartFile.getName();
                String fileName = multipartFile.getOriginalFilename();
                paraMap.put(name, fileName);
                continue;
            } else if (value instanceof MultipartFile[]) {
                MultipartFile[] arr = (MultipartFile[]) value;
                if (arr.length == 0) {
                    continue;
                }
                String name = arr[0].getName();
                StringBuilder sb = new StringBuilder(arr.length);
                for (MultipartFile multipartFile : arr) {
                    sb.append(multipartFile.getOriginalFilename());
                    sb.append(",");
                }
                paraMap.put(name, removeSuffix(sb.toString(), ","));
                continue;
            } else if (value instanceof List) {
                List<?> list = (List<?>) value;
                AtomicBoolean isSkip = new AtomicBoolean(false);
                for (Object o : list) {
                    if ("StandardMultipartFile".equalsIgnoreCase(o.getClass().getSimpleName())) {
                        isSkip.set(true);
                        break;
                    }
                }
                if (isSkip.get()) {
                    paraMap.put(parameterName, "此参数不能序列化为json");
                    continue;
                }
            }

            // 参数名
            RequestParam requestParam = methodParam.getAnnotation(RequestParam.class);
            String paraName = parameterName;
            if (requestParam != null && StrUtil.isNotBlank(requestParam.value())) {
                paraName = requestParam.value();
            }
            if (value == null) {
                paraMap.put(paraName, null);
            } else if (ClassUtils.isPrimitiveOrWrapper(value.getClass())) {
                paraMap.put(paraName, value);
            } else if (value instanceof InputStream) {
                paraMap.put(paraName, "InputStream");
            } else if (value instanceof InputStreamSource) {
                paraMap.put(paraName, "InputStreamSource");
            } else {
                try {
                    paraMap.put(paraName, value);
                } catch (Exception e) {
                    e.printStackTrace();
                    paraMap.put(paraName, "此参数不能序列化为json");
                }
            }
        }

        // 请求参数
        if (paraMap.isEmpty()) {
            template.append("\n");
        } else {
            template.append("=== Parameters ===  {}\n");
            templateArgs.add(JSONUtil.toJsonStr(paraMap));
        }

        if (requestBodyValue != null) {
            template.append("=== Body       ===  {}\n");
            templateArgs.add(JSONUtil.toJsonStr(requestBodyValue));
        }

        template.append("================   Request End   ================\n");
        log.info(template.toString(), templateArgs.toArray());
    }

    public static MethodParameter getMethodParameter(Method method, int parameterIndex) {
        MethodParameter methodParameter = new SynthesizingMethodParameter(method, parameterIndex);
        methodParameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
        return methodParameter;
    }

    public static String removeSuffix(CharSequence str, CharSequence suffix) {
        if (StrUtil.isEmpty(str) || StrUtil.isEmpty((suffix))) {
            return "";
        }
        final String str2 = str.toString();
        if (str2.endsWith(suffix.toString())) {
            return str2.substring(str2.length() - suffix.length());
        }
        return str2;
    }
}
